'''
An RPG Modification for Source games using Source Python.

    Copyright (C) 2012 Steven Hartin

    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.

Created on 28 Dec 2012

@author: Steven Hartin
@file: ../sourcerpg/players/player.py
'''

from messages import SayText2
from players.entity import PlayerEntity
from players.helpers import userid_from_index

from sourcerpg.config import config
from sourcerpg.utils.baseclass import BaseClass
from sourcerpg.database import database
from sourcerpg.skills import skills


class Player(PlayerEntity):
    """Player class to manage player specific actions in SourceRPG"""
    def __init__(self, index):
        """Setup the cache for the player from the database

        @param int index The player entity index

        """
        PlayerEntity.__init__(self, index)
        object.__setattr__(self, "cache", database["players"][self.uniqueid])

    def __getitem__(self, key):
        """Obtain a value from the player entity or the player's cache

        @param str key The name of the item to obtain
        @return mixed The value from either player's entity or cache

        """
        if hasattr(self, key):
            return PlayerEntity.__getitem__(key)
        if key in self.cache:
            return self.cache[key]

    def __setitem__(self, key, value):
        """Set a new value for an existing entity attribute or cache item

        @param str key The name of the attribute to assign
        @param mixed value The value to assign to

        """
        if hasattr(self, key):
            PlayerEntity.__setitem__(self, key, value)
        else:
            self.cache[key] = value

    def __award_levels(self, amount_of_levels):
        """Award amount of levels to a player

        @param int amount_of_levels A positive amount of levels to award

        """
        assert amount_of_levels > 0
        credits_ = amount_of_levels * config["xp"].as_int("credits_per_level")
        self["credits"] += credits_

    def add_xp(self, experience):
        """Adds experience for a player and returns the new / old levels

        @param float experience The amount of experience to award
        @return tuple Two item, the first is the old values of get_level() and
            the second value is the new values of get_level()

        """
        old_values = self.get_level()
        self["xp"] += experience
        new_values = self.get_level()
        num_levels = 0
        while (self["xp"] >= new_values[2]):
            num_levels += 1
            self["level"] += 1
            self["xp"] -= new_values[2]
            new_values = self.get_level()
        if num_levels > 0:
            # Awarded a level
            self.__award_levels(new_values[0] - old_values[0])
        return (old_values, new_values)

    def get_level(self):
        """Obtains the current level from the xp value for the player

        @return tuple The current level, current experience and amount of
            experience to the next level. (level, current_xp, next_level_xp)

        """
        current_level = self.cache["level"]
        current_xp = self.cache["xp"]
        next_xp = self.get_xp_for_level(current_level + 1)
        return (current_level, current_xp, next_xp)

    @staticmethod
    def get_xp_for_level(level):
        """@return int Amount of total xp needed for the level passed in"""
        exp_growth = config["xp"]["levels"].as_float(
            "exponential_growth_per_level")
        lin_growth = config["xp"]["levels"].as_float("linear_growth_per_level")
        initial = config["xp"]["levels"].as_int("initial_level_experience")
        if level <= 1:
            # We start on level 1
            return 0
        return (((level - 2) ** exp_growth) * lin_growth +
            initial * (level - 1))

    def send_message(self, message, index=None):
        """Sends a message to the player

        @param str message A message to send

        """
        message = SayText2(message, index if index is not None else self.index,
            self.index)
        message.send()

    def change_skill_level(self, skill_name, level):
        """Updates the level of a skill to a new value.

        @param str skill_name The name of the skill to alter
        @param int level The level of the skill

        """
        if skill_name not in skills:
            raise KeyError("Skill %s is not loaded or does not exist" % (
                skill_name))
        if skill_name not in self.cache["skills"]:
            self.cache["skills"][skill_name] = {}
        self.cache["skills"][skill_name]["level"] = level

    def get_skill_level(self, skill_name):
        """Obtains the level of a skill

        @param str skill_name The name of the skill to obtain the level for
        @return int The current level of that skill (if the user has never
            had the level stored in cache, 0 is returned)

        """
        if skill_name not in skills:
            raise KeyError("Skill %s is not loaded or does not exist" % (
                skill_name))
        if skill_name in self.cache["skills"]:
            return self.cache["skills"][skill_name]["level"]
        return 0

    def upgrade_skill(self, skill_name, ignore_credits=False):
        """Attempt to upgrade a skill 1 level.

        @param str skill_name The name of the skill to upgrade
        @param bool ignore_credits Whether to ignore the credits. This will
            not deduct any credits from the player's score, and it will
            also succeed as long as this user is not already at the skill's
            maximum level.
        @return bool Whether the skill was successfully upgraded

        """
        if skill_name not in skills:
            return False
        skill = skills[skill_name]
        if skill_name not in self.cache["skills"]:
            self.cache["skills"][skill_name] = {}
            self.cache["skills"][skill_name]["level"] = 0
        level = self.cache["skills"][skill_name]["level"]
        credit_cost = skill.get_credit_cost(level + 1)
        if level >= skill.max_level:
            return False
        if ignore_credits is False:
            credits_ = self.cache["credits"]
            if credits_ < credit_cost:
                return False
        self.cache["skills"][skill_name]["level"] += 1
        self.cache["credits"] -= credit_cost
        skill.apply_skill(self, self.cache["skills"][skill_name]["level"])
        return True

    def downgrade_skill(self, skill_name, ignore_credits=False):
        """Attempt to downgrade a skill 1 level.

        @param str skill_name The name of the skill to downgrade
        @param bool ignore_credits Whether to ignore the credits. This will
            not add any credits to the player's score for selling the skill.
        @return bool Whether the skill was successfully downgraded

        """
        if skill_name not in skills:
            return False
        skill = skills[skill_name]
        if skill_name not in self.cache["skills"]:
            self.cache["skills"][skill_name] = {}
            self.cache["skills"][skill_name]["level"] = 0
        level = self.cache["skills"][skill_name]["level"]
        if level <= 0:
            return False
        if ignore_credits is False:
            credit_cost = skill.get_credit_cost(level)
            credits_acquired = int(credit_cost * 0.75)
            self.cache["credits"] += credits_acquired
        self.cache["skills"][skill_name]["level"] -= 1
        skill.apply_skill(self, self.cache["skills"][skill_name]["level"])
        return True
